﻿using System;
#if WINDOWS_PHONE
using StyleMVVM.Data.Collections;
#else
using System.Collections.Concurrent;
#endif
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data.Activation;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.Data;
#if !DOT_NET
using Windows.Foundation.Collections;
using Windows.Foundation.Metadata;

#endif

namespace StyleMVVM.Data.Transform
{
	public sealed class TransformService : ITransformService
	{
		private readonly GenericListTransform genericListStrategy;
		private readonly PrimitiveTransformStrategy primitiveTransformStrategy;
		private readonly ConcurrentDictionary<Type, ITransformStrategy> transformStrategy;
		private static readonly string supplemental = typeof(TransformService).FullName;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(TransformService)).
			          As(typeof(ITransformService)).
			          ImportProperty("ReflectionService").
			          ImportProperty("ActivationService").
			          AndSharedPermenantly();
		}

		public TransformService()
		{
			transformStrategy =
				new ConcurrentDictionary<Type, ITransformStrategy>();

			primitiveTransformStrategy = new PrimitiveTransformStrategy();

			genericListStrategy = new GenericListTransform { TransformService = this };
		}

		public IReflectionService ReflectionService { get; set; }

		public IActivationService ActivationService { get; set; }

		public ITransformMap AddTransform()
		{
			return new TransformMap(this);
		}

		[DefaultOverload]
		public void AddTransforms(IEnumerable<ITransformMapInfo> newTransforms)
		{
			foreach (ITransformMapInfo transform in newTransforms)
			{
				ITransformStrategy newStrategy = Activator.CreateInstance(
					typeof(CompiledTransformStrategy<,>).MakeGenericType(transform.InType, transform.OutType)) as ITransformStrategy;

				newStrategy.Initalize();

				transformStrategy.TryAdd(
					typeof(CompiledTransformStrategy<,>).MakeGenericType(transform.InType, transform.OutType),
					newStrategy);
			}
		}

		public void AddTransforms(params ITransformMapInfo[] newTransforms)
		{
			if (newTransforms != null)
			{
				AddTransforms((IEnumerable<ITransformMapInfo>)newTransforms);
			}
		}

		public object Transform(object transformValue, Type tranformType)
		{
			return Transform(transformValue, tranformType, false, true);
		}

		public object Transform(object transformValue, Type tranformType, bool allowErrors, bool looseTransform)
		{
			if (transformValue == null)
			{
				return null;
			}

			if (tranformType == null)
			{
				return null;
			}

			// if we are transforming the same just make a clone
			if (transformValue.GetType() == tranformType)
			{
				return ActivationService.DeepClone(transformValue);
			}

			Type startType = transformValue.GetType();
			ITransformStrategy transformAction = null;

			try
			{
				transformAction =
					FindTransform(
						typeof(CompiledTransformStrategy<,>).MakeGenericType(
							transformValue.GetType(), tranformType));
			}
			catch (Exception exp)
			{
				if (!allowErrors)
				{
					throw;
				}

				Logger.Error("Exception creating transforming method ", supplemental, exp);
			}

			return InternalTransform(transformAction,
			                         transformValue,
			                         tranformType,
			                         allowErrors,
			                         looseTransform);
		}

		private object InternalTransform(ITransformStrategy transformAction,
		                                 object transformValue,
		                                 Type targetType,
		                                 bool allowErrors,
		                                 bool looseTransform)
		{
			if (transformAction != null)
			{
				try
				{
					if (!transformAction.IsLooseTranform || looseTransform)
					{
						return transformAction.Transform(transformValue, targetType);
					}

					throw new Exception("Loose transforms aren't allowed");
				}
				catch (Exception exp)
				{
					if (!allowErrors)
					{
						throw;
					}

					Logger.Error("Exception thrown while transforming type", supplemental, exp);
				}
			}
			else if (!allowErrors)
			{
				throw new Exception(
					string.Format("Cant convert from {0} to {1}",
					              transformAction.GetType().FullName,
					              transformAction.GetType().FullName));
			}
			else
			{
				Logger.Error("Could not find Transform strategy: " + transformAction.GetType().FullName, supplemental);
			}

			return null;
		}

		private ITransformStrategy FindTransform(Type iTransformStrategy)
		{
			ITransformStrategy returnValue;

			if (!transformStrategy.TryGetValue(iTransformStrategy, out returnValue))
			{
				Type inType = iTransformStrategy.GenericTypeArguments[0];
				Type outType = iTransformStrategy.GenericTypeArguments[1];

				if (inType.GetTypeInfo().IsPrimitive || inType == typeof(string))
				{
					if (outType.GetTypeInfo().IsPrimitive || outType == typeof(string))
					{
						returnValue = primitiveTransformStrategy;
					}
				}
				else if (outType.GetTypeInfo().IsPrimitive)
				{
					returnValue = null;
				}
				else
				{
					returnValue = processForContainerStrategy(inType, outType);

					if (returnValue == null)
					{
						Type newCompiledType = typeof(CompiledTransformStrategy<,>).
							MakeGenericType(iTransformStrategy.GenericTypeArguments);

						ITransformMap transformMap = createTransformMap(inType, outType);

						returnValue = Activator.CreateInstance(newCompiledType,
						                                       this,
						                                       ActivationService,
						                                       ReflectionService,
						                                       transformMap) as ITransformStrategy;

						returnValue.Initalize();

						transformStrategy.TryAdd(iTransformStrategy, returnValue);
					}
				}
			}

			return returnValue;
		}

		private ITransformStrategy processForContainerStrategy(Type inType, Type outType)
		{
			bool inIListGeneric = false;
			bool inIDictionaryGeneric = false;
			bool inIMap = false;

			bool outIListGeneric = false;
			bool outIDictionaryGeneric = false;
			bool outIMap = false;
			Type transformType = null;

			foreach (Type implementedInterface in inType.GetTypeInfo().ImplementedInterfaces)
			{
				if (implementedInterface.IsConstructedGenericType)
				{
					Type genericInterface =
						implementedInterface.GetTypeInfo().GetGenericTypeDefinition();

					if (genericInterface == typeof(IList<>))
					{
						inIListGeneric = true;
					}
					else if (genericInterface == typeof(IDictionary<,>))
					{
						inIDictionaryGeneric = true;
					}
#if !DOT_NET
					else if (genericInterface == typeof(IObservableMap<,>))
					{
						inIMap = true;
					}
#endif
				}
			}

			foreach (Type implementedInterface in outType.GetTypeInfo().ImplementedInterfaces)
			{
				if (implementedInterface.IsConstructedGenericType)
				{
					Type genericInterface =
						implementedInterface.GetTypeInfo().GetGenericTypeDefinition();

					if (genericInterface == typeof(IList<>))
					{
						outIListGeneric = true;
					}
					else if (genericInterface == typeof(IDictionary<,>))
					{
						outIDictionaryGeneric = true;
					}
#if !DOT_NET
					else if (genericInterface == typeof(IObservableMap<,>))
					{
						outIMap = true;
					}
#endif
				}
			}

			if (inIListGeneric && outIListGeneric)
			{
				return genericListStrategy;
			}

			return null;
		}

		private ITransformMap createTransformMap(Type inType, Type outType)
		{
			bool inIsTranformable = false;
			bool outIsTransformable = false;
			ITransformMap newTransformMap = new TransformMap(this);

			newTransformMap.InType(inType).OutType(outType);

			List<PropertyInfo> propertiesToProcess =
				new List<PropertyInfo>(inType.GetTypeInfo().DeclaredProperties);

			foreach (Attribute customAttribute in
				inType.GetTypeInfo().GetCustomAttributes())
			{
				if (customAttribute is ITransformableAttribute)
				{
					inIsTranformable = true;
					break;
				}
			}

			foreach (Attribute customAttribute in
				outType.GetTypeInfo().GetCustomAttributes())
			{
				if (customAttribute is ITransformableAttribute)
				{
					outIsTransformable = true;
					break;
				}
			}

			if (propertiesToProcess.Count > 0)
			{
				processInSideOfTransform(newTransformMap,
				                         inType,
				                         outType,
				                         propertiesToProcess);
			}

			if (propertiesToProcess.Count > 0)
			{
				processOutSideOfTransform(newTransformMap,
				                          inType,
				                          outType,
				                          propertiesToProcess);
			}

			return newTransformMap;
		}

		private void processOutSideOfTransform(ITransformMap transformMap,
		                                       Type inType,
		                                       Type outType,
		                                       List<PropertyInfo> propertiesToProcess)
		{
			foreach (PropertyInfo declaredProperty in outType.GetTypeInfo().DeclaredProperties)
			{
				if (declaredProperty.GetMethod == null)
				{
					continue;
				}

				if (declaredProperty.GetMethod.IsStatic)
				{
					continue;
				}

				if (!declaredProperty.GetMethod.IsPublic)
				{
					continue;
				}

				if (declaredProperty.GetIndexParameters().Length != 0)
				{
					continue;
				}

				bool foundTransform = false;
				PropertyInfo destinationProp = null;

				foreach (Attribute attribute in declaredProperty.GetCustomAttributes())
				{
					ITransformAttribute transformAttribute = attribute as ITransformAttribute;

					if (transformAttribute != null &&
					    !string.IsNullOrEmpty(transformAttribute.TransformName))
					{
						destinationProp =
							propertiesToProcess.FirstOrDefault(
								x => x.Name == transformAttribute.TransformName);

						if (destinationProp != null)
						{
							string propertyName = destinationProp.Name;
							string targetName = declaredProperty.Name;

							if (transformAttribute.TransformType != null)
							{
								if (transformAttribute.TransformType != inType)
								{
									continue;
								}
							}

							PropertyInfo propertyType = Data.ReflectionService.GetPropertyInfo(inType, propertyName);
							PropertyInfo targetType = Data.ReflectionService.GetPropertyInfo(outType, targetName);

							if (propertyType != null && targetType != null &&
							    declaredProperty.GetMethod.IsPublic && !declaredProperty.GetMethod.IsStatic &&
							    targetType.GetMethod.IsPublic && !targetType.SetMethod.IsStatic)
							{
								transformMap.MapProperty.
								             From(propertyName, propertyType.PropertyType).
								             To(targetName, targetType.PropertyType);

								foundTransform = true;
							}
						}
					}
				}

				if (!foundTransform)
				{
					destinationProp =
						propertiesToProcess.FirstOrDefault(
							x => x.Name == declaredProperty.Name);
					if (destinationProp != null)
					{
						string propertyName = destinationProp.Name;
						string targetName = declaredProperty.Name;

						PropertyInfo propertyType = Data.ReflectionService.GetPropertyInfo(inType, propertyName);
						PropertyInfo targetType = Data.ReflectionService.GetPropertyInfo(outType, targetName);

						if (propertyType != null && targetType != null &&
						    declaredProperty.GetMethod.IsPublic && !declaredProperty.GetMethod.IsStatic &&
						    targetType.GetMethod.IsPublic && !targetType.SetMethod.IsStatic)
						{
							transformMap.MapProperty.
							             From(propertyName, propertyType.PropertyType).
							             To(targetName, targetType.PropertyType);

							foundTransform = true;
						}
					}
				}

				if (foundTransform && destinationProp != null)
				{
					propertiesToProcess.Remove(destinationProp);
				}
			}
		}

		private void processInSideOfTransform(ITransformMap transformMap,
		                                      Type inType,
		                                      Type outType,
		                                      List<PropertyInfo> propertiesToProcess)
		{
			for (int i = 0; i < propertiesToProcess.Count;)
			{
				PropertyInfo propertyInfo = propertiesToProcess[i];

				if (propertyInfo.SetMethod == null)
				{
					continue;
				}

				if (propertyInfo.SetMethod.IsStatic)
				{
					propertiesToProcess.RemoveAt(i);

					continue;
				}

				if (!propertyInfo.SetMethod.IsPublic)
				{
					propertiesToProcess.RemoveAt(i);

					continue;
				}

				if (!propertyInfo.CanWrite)
				{
					propertiesToProcess.RemoveAt(i);

					continue;
				}

				if (propertyInfo.GetIndexParameters().Length != 0)
				{
					propertiesToProcess.RemoveAt(i);

					continue;
				}

				bool processed = false;

				foreach (Attribute attribute in propertyInfo.GetCustomAttributes())
				{
					ITransformAttribute transformAttribute = attribute as ITransformAttribute;

					if (transformAttribute != null)
					{
						string propertyName = propertyInfo.Name;
						string targetName = propertyInfo.Name;

						if (!string.IsNullOrEmpty(transformAttribute.PropertyName))
						{
							propertyName = transformAttribute.PropertyName;
						}

						if (!string.IsNullOrEmpty(transformAttribute.TransformName))
						{
							targetName = transformAttribute.TransformName;
						}

						if (transformAttribute.TransformType != null)
						{
							if (transformAttribute.TransformType != outType)
							{
								continue;
							}
						}

						PropertyInfo propertyType = Data.ReflectionService.GetPropertyInfo(inType, propertyName);
						PropertyInfo targetType = Data.ReflectionService.GetPropertyInfo(outType, targetName);

						if (propertyType != null && targetType != null &&
						    propertyInfo.GetMethod.IsPublic && !propertyInfo.GetMethod.IsStatic &&
						    targetType.GetMethod.IsPublic && !targetType.SetMethod.IsStatic)
						{
							transformMap.MapProperty.
							             From(propertyName, propertyType.PropertyType).
							             To(targetName, targetType.PropertyType);

							processed = true;
						}
					}
				}

				if (processed)
				{
					propertiesToProcess.RemoveAt(i);
				}
				else
				{
					i++;
				}
			}
		}
	}
}